package com.honkentuber.chat;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import com.google.appengine.api.datastore.Key;
import com.honkentuber.Database;
import com.honkentuber.MyUser;
import com.honkentuber.TimingLogger;

public class ChatDatabase extends Database {

  private final static Logger LOG = Logger.getLogger(
      ChatDatabase.class.getName());

  public ChatDatabase(PersistenceManager pm, TimingLogger timingLogger) {
    super(pm, timingLogger);
  }

  @SuppressWarnings("unchecked")
  public Room fetchDefaultRoom() {
    Query query = pm.newQuery(Room.class);
    query.setFilter("creatorKey == null");

    List<Room> rooms = (List<Room>) query.execute();
    if (!rooms.isEmpty()) {
      timingLogger.recordTiming("room_load_default");
      return rooms.get(0);
    }

    Room room = new Room(null, "default");
    pm.makePersistent(room);
    timingLogger.recordTiming("room_create_default");
    return room;
  }

  private void cacheUsersForRoom(Room room) {
    myUserCache.add(room.getCreatorKey());
    for (Presence presence : room.getPresences()) {
      myUserCache.add(presence.getUserKey());
    }
  }

  @SuppressWarnings("unchecked")
  public Room fetchRoom(Key key) {
    Query query = pm.newQuery(Room.class);
    query.declareParameters("com.google.appengine.api.datastore.Key keyParam");
    query.setFilter("key == keyParam");
    List<Room> rooms = (List<Room>) query.execute(key);
    if (!rooms.isEmpty()) {
      Room room = rooms.get(0);
      cacheUsersForRoom(room);
      timingLogger.recordTiming("room_load");
      return room;
    }

    return null;
  }

  /**
   * @return Rooms created by creatorEmail
   */
  @SuppressWarnings("unchecked")
  public List<Room> fetchRoomsByCreatorEmail(String creatorEmail) {
    if (creatorEmail == null || creatorEmail.length() <= 0) {
      return Collections.emptyList();
    }

    Query userQuery = pm.newQuery(MyUser.class);
    userQuery.declareParameters("com.honkentuber.MyUser emailParam");
    userQuery.setFilter("email == emailParam");
    List<MyUser> creators = (List<MyUser>) userQuery.execute(creatorEmail);
    if (creators.isEmpty()) {
      return Collections.emptyList();
    }

    MyUser creator = creators.get(0);
    myUserCache.add(creator);
    return fetchRoomsByCreator(creator.getKey());
  }

  @SuppressWarnings("unchecked")
  public List<Room> fetchRoomsByCreator(Key creatorKey) {
    Query query = pm.newQuery(Room.class);
    query.declareParameters(
        "com.google.appengine.api.datastore.Key creatorKeyParam");
    query.setFilter("creatorKey == creatorKeyParam");
    List<Room> rooms = (List<Room>) query.execute(creatorKey);
    for (Room room : rooms) {
      cacheUsersForRoom(room);
    }
    return rooms;
  }

  @SuppressWarnings("unchecked")
  public List<Room> fetchRooms() {
    Query query = pm.newQuery(Room.class);
    List<Room> rooms = (List<Room>) query.execute();
    for (Room room : rooms) {
      cacheUsersForRoom(room);
    }
    return rooms;
  }

  /**
   * @return Number of messages deleted.
   */
  public long deleteMessage(Key key) {
    Query msgQuery = pm.newQuery(Message.class);
    msgQuery.declareParameters(
        "com.google.appengine.api.datastore.Key keyParam");
    msgQuery.setFilter("key == keyParam");
    return msgQuery.deletePersistentAll(key);
  }

  /**
   * @return Number of messages deleted. -1 if we do not delete a room.
   */
  public long deleteRoom(Key key) {
    Query msgQuery = pm.newQuery(Message.class);
    msgQuery.declareParameters(
        "com.google.appengine.api.datastore.Key roomKeyParam");
    msgQuery.setFilter("roomKey == roomKeyParam");
    long msgCount = msgQuery.deletePersistentAll(key);

    Query roomQuery = pm.newQuery(Room.class);
    roomQuery.declareParameters(
        "com.google.appengine.api.datastore.Key keyParam");
    roomQuery.setFilter("key == keyParam");
    long roomCount = roomQuery.deletePersistentAll(key);

    if (msgCount > 0) {
      return msgCount;
    }

    timingLogger.recordTiming("room_delete");
    return roomCount <= 0 ? -1 : 0;
  }

  /**
   * @return # messages deleted, or -1 if no user found.
   */
  @SuppressWarnings("unchecked")
  public long deleteUser(Key userKey) {
    List<MyUser> users = fetchUsers(userKey);
    long result = users.isEmpty() ? -1 : 0;
    for (MyUser user : users) {
      Query presenceQuery = pm.newQuery(Presence.class);
      presenceQuery.declareParameters(
          "com.google.appengine.api.datastore.Key userKeyParam");
      presenceQuery.setFilter("userKey == userKeyParam");
      presenceQuery.deletePersistentAll(user.getKey());

      Query msgQuery = pm.newQuery(Message.class);
      msgQuery.declareParameters(
          "com.google.appengine.api.datastore.Key authorKeyParam");
      msgQuery.setFilter("authorKey == authorKeyParam");
      result += presenceQuery.deletePersistentAll(user.getKey());

      Query roomQuery = pm.newQuery(Room.class);
      roomQuery.declareParameters(
          "com.google.appengine.api.datastore.Key creatorKeyParam");
      roomQuery.setFilter("creatorKey == creatorKeyParam");
      List<Room> rooms = (List<Room>) roomQuery.execute(user.getKey());
      for (Room room : rooms) {
        deleteRoom(room.getKey());
      }

      super.deleteUser(userKey);
    }
    timingLogger.recordTiming("chat_user_delete");
    return result;
  }

  /**
   * @return Room, or null if we failed to create one.
   */
  public Room createRoom(MyUser user, String name) {
    if (user == null) {
      LOG.warning("can not create room with no user");
      return null;
    }
    if (name == null || name.length() == 0) {
      LOG.warning("can not create room with empty name, user = " + user);
      return null;
    }

    Room room = new Room(user.getKey(), name);
    pm.makePersistent(room);
    timingLogger.recordTiming("room_create");
    return room;
  }

  @SuppressWarnings("unchecked")
  public long lastSequenceId(Room room) {
    Query query = pm.newQuery(Message.class);
    query.setOrdering("sequenceNumber desc");
    query.setFilter("roomKey == roomKeyParam");
    query.setRange(0, 1);
    query.declareParameters(
        "com.google.appengine.api.datastore.Key roomKeyParam");
    List<Message> messages = (List<Message>) query.execute(room.getKey());
    long result = messages.isEmpty()
        ? 1 : messages.get(0).getSequenceNumber();
    timingLogger.recordTiming("msg_last_seq");
    return result;
  }

  /**
   * Store new message if specified, and fetch recent messages (including
   * newly saved one).
   * @return
   */
  @SuppressWarnings("unchecked")
  public List<Message> storeAndFetchMessages(MyUser user, Room room,
      String msgContent, Long seq) {
    room.updatePresence(user);

    // Build query, sorted in descending order of create time.
    Query query = pm.newQuery(Message.class);
    query.setOrdering("sequenceNumber desc");

    // If sequence specified, fetch everything more recent than that up
    // to 100. Otherwise, fetch most recent 5.
    List<Message> msgs = null;
    if (seq != null) {
      query.declareParameters(
          "com.google.appengine.api.datastore.Key roomKeyParam, long seqParam");
      query.setFilter("roomKey == roomKeyParam && sequenceNumber > seqParam");
      query.setRange(0, 100);
      msgs = (List<Message>) query.execute(room.getKey(), seq.longValue());
    } else {
      query.declareParameters(
          "com.google.appengine.api.datastore.Key roomKeyParam");
      query.setFilter("roomKey == roomKeyParam");
      query.setRange(0, 5);
      msgs = (List<Message>) query.execute(room.getKey());
    }

    // Get messages and last message ID.
    timingLogger.recordTiming("msgs_load");
    long lastSequenceId = msgs.isEmpty()
        ? lastSequenceId(room) : msgs.get(0).getSequenceNumber();

    // Persist message.
    if (msgContent != null && msgContent.length() > 0) {
      Message msg = new Message(lastSequenceId + 1, room.getKey(),
          user == null ? null : user.getKey(), msgContent);
      pm.makePersistent(msg);
      List<Message> resultPlusOne = new ArrayList<Message>(msgs.size() + 1);
      resultPlusOne.addAll(msgs);
      resultPlusOne.add(0, msg);
      msgs = resultPlusOne;
    }
    timingLogger.recordTiming("msg_store_and_fetch");

    for (Message msg : msgs) {
      myUserCache.add(msg.getAuthorKey());
    }
    return msgs;
  }
}
